#!/usr/bin/env python
# -*- coding:utf-8 -*-
'''
@File    :   Double_underline.py
@Time    :   2025/01/15 19:47:35
@Author  :   xiangyu Chen
@Version :   1.0
@Contact :   yu112yu@sina.com
@License :   (C)Copyright 2017-2018, Liugroup-NLPR-CASIA
@python_requires    :   None
@Desc    :   定制类，类似__slots__这种形如__xxx__的变量或者函数名就要注意，这些在Python 中是有特殊用途的。
'''

# __slots__变量
# 在定义 class 的时候，定义一个特殊的__slots__变量，来限制该 class 实例能添加的属性。


import os
import pprint
import sys


class Student1(object):
    __slots__ = ('name', 'age')  # 用tuple定义允许绑定的属性名称


s1 = Student1()

s1.name = 'Michael'

s1.age = 25
try:
    s1.score = 95
except AttributeError as e:
    print('Student1 __slots__ 限制了只能添加属性 name, age, 无法添加 score 属性：', e)
# 使用__slots__要注意，__slots__定义的属性仅对当前类实例起作用，对继承的子类是不起作用的：


class GraduateStudent(Student1):
    pass


g = GraduateStudent()

g.score = 95

print('GraduateStudent 子类不受父类_slots__影响， g.score :', g.score)  # 95


# __len__()方法我们也知道是为了能让class 作用于 len()函数。

class HB(object):
    def __len__(self):
        return 5


hb = HB()

print('类定义__len__()方法后，实例可以 len(hb) :', len(hb))  # 5

# __str__方法是一个特殊的内置方法，它在使用print()函数打印对象或者使用str()函数强制转换对象为字符串时生效。__str__方法的目的是返回一个对象的“字符串表示”，这样可以提供关于对象的有用信息，而不仅仅是它的内存地址。


class HB1(object):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'Hello, %s!' % self.name
    __repr__ = __str__


hb1 = HB1('one')
print('没有__str__，打印实例显示 的是内存地址, hb:', hb)
print('HB1定义了__str__，hb1：', hb1)
print('HB1定义了__str__，str(hb1)：', str(hb1))

# __repr__()返回程序开发者看到的字符串，也就是说，__repr__()是为调试服务的。
# 通常__str__()和__repr__()代码都是一样的，所以，有个偷懒的写法： __repr__ = __str__


# __iter__(self) 此方法会在需要为一个容器创建 iterator 时被调用。 此方法应当返回一个新的迭代器对象，
# 它可以对容器中的所有对象执行迭代。 对于映射，它应当对窗口中的键执行迭代。
# 通常情况下，__iter__() 方法应返回一个实现了迭代器协议的对象，
# 即具有 __next__() 方法的对象，
# 或者自身就是一个生成器（包含 yield 关键字）。
# 迭代器会持续产生序列中的下一个元素，直到序列被耗尽。
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1  # 初始化两个计数器 a，b

    def __iter__(self):  # 返回具有 __next__() 方法的对象，
        return self  # 实例本身就是迭代对象，故返回自己。

    def __next__(self):
        a, b = self.a, self.b
        self.a, self.b = b, a + b
        if a > 100000:  # 退出循环的条件
            raise StopIteration()
        return a  # 返回下一个值


for n in Fib():
    print('Fib()：', n)


class MyRange:
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __iter__(self):  # 自身就是一个生成器（包含 yield 关键字）
        current = self.start
        while current < self.end:
            yield current
            current += 1


# 使用示例
my_range = MyRange(0, 5)

for num in my_range:
    print('MyRange(0, 5)', num)

# __getitem__,可以实现索引取值、切片


class CustomList(object):
    def __getitem__(self, n):
        if isinstance(n, int):  # n 是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):  # n 是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L


custom_list = CustomList()

print('custom_list[1:3]', custom_list[1:3])


# __getattr__()方法，动态返回属性或函数。

class MyObject(object):
    def __getattr__(self, attr):
        if attr == 'x':
            return 10
        elif attr == 'y':
            return lambda: 20
        else:
            raise AttributeError(attr)


obj = MyObject()

print('obj.x:', obj.x)

print('obj.y:', obj.y())  # 返回函数,需要加()调用

try:
    print('obj.z:', obj.z)
except AttributeError as e:
    print('obj.z 找不到属性：', e)

# 利用完全动态的__getattr__，可以写出一个链式调用：


class Chain(object):
    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, name):
        return Chain('%s/%s' % (self._path, name))

    def __str__(self):
        return self._path

    __repr__ = __str__


lPath = Chain().status.user.timeline.list
print('Chain().status.user.timeline.list ,lPath: ', lPath)
print('Chain().status.user.timeline.list ,lPath._path: ', lPath._path)

# =============================exercises============================
# 写出支持Chain().users('michael').repos 的链式调用：


class Chain2(object):
    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, name):
        return Chain2('%s/%s' % (self._path, name))

    def __str__(self):
        return self._path

    __repr__ = __str__

    # def users(self, name):
    #     return Chain2('%s/users/%s' % (self._path, name))

    # def repos(self):
    #     return Chain2('%s/repos' % self._path)

    # 请在 Chain2 类中实现 __call__ 方法，使得 Chain().users('michael').repos() 等价于 Chain2('users/michael/repos')

    def __call__(self, *args, **kwargs):
        if len(args) == 0:
            return self  # 没有参数，返回Chain2本身
        # 把*args传入的参数（元组）用/拼接后再接到path后
        return Chain2('%s/%s' % (self._path, '/'.join(map(str, args))))

    # 请在 Chain2 类中实现 __getitem__ 方法，使得 Chain().users('michael')[0] 等价于 Chain2('users/michael/0')

    def __getitem__(self, item):
        return Chain2('%s/%s' % (self._path, item))  # 拼索引
        # return Chain2('%s/%s/%s' % (self._path, '/'.join(map(str, self._path.split('/')[:-1])), item))


print('Chain2().users(\'michael\').repos(): ',
      Chain2().users('michael').repos())

print('Chain2().users(\'michael\')[0][1]: ',  Chain2().users(
    'michael', 'load')[0][1])

print('Chain2().users(\'michael\')[1].load()[\'repos\']: ',
      Chain2().users('michael')[1].load()['repos'])


# 当我们调用实例方法时，我们用 instance.method()来调用。也可以定义__call__ ，直接在实例本身上调。


class NoCall(object):
    def __init__(self, name):
        self.name = name


noCall = NoCall('HAHA')
try:
    noCall()
except Exception as e:
    except_type, except_value, except_traceback = sys.exc_info()
    except_file = os.path.split(
        except_traceback.tb_frame.f_code.co_filename)
    exc_dict = {
        "报错类型": except_type,
        "报错信息": except_value,
        "报错文件": except_file,
        "报错行数": except_traceback.tb_lineno,
    }
    pprint.pprint(exc_dict)
    print('NoCall() 找不到方法：', e)


class HasCall(object):
    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        return self.name


hasCall = HasCall('HAHA')

print('hasCall.name,hasCall():', hasCall.name, hasCall())

# 判断一个对象是否能被调用。

print('callable(hasCall):', callable(hasCall))
print('callable(NoCall):', callable(noCall))


